Conversation
Introduces multi-tenant organization support via Clerk Organizations and
refactors the entire authentication layer into a dedicated feature module
under apps/web/modules/auth/, separating routing from domain logic.
## Auth module architecture
Extracted all auth UI into modules/auth/ui/ following a views/layouts/components
structure:
- AuthLayout — full-screen centered wrapper for all auth pages
- SignInView — <SignIn routing=hash /> (hash routing prevents full-page
navigations on Clerk multi-step flows)
- SignUpView — <SignUp routing=hash />
- OrgSelectionView — <OrganizationList hidePersonal skipInvitationScreen> with
post-selection redirect to /
- AuthGuard — Convex Authenticated/Unauthenticated/AuthLoading guard;
shows SignInView inline when unauthenticated (no redirect)
- OrganizationGuard — useOrganization() guard; renders OrgSelectionView inside
AuthLayout when no active org is present
## Guard composition at dashboard layout level
app/(dashboard)/layout.tsx wraps all dashboard routes with:
<AuthGuard>
<OrganizationGuard>
{children}
</OrganizationGuard>
</AuthGuard>
Both conditions (Convex session + Clerk org) must be satisfied before any
dashboard page renders. No per-page checks required.
## Organization redirect in middleware
proxy.ts extended with org enforcement:
- isOrgFreeRoute matcher covers /sign-in, /sign-up, /org-selection
- Authenticated users without an active orgId are redirected to
/org-selection?redirectUrl=<original> unless already on an org-free route
## Auth pages refactored to module views
- app/(auth)/layout.tsx → delegates to <AuthLayout>
- app/(auth)/sign-in/page.tsx → delegates to <SignInView>
- app/(auth)/sign-up/page.tsx → delegates to <SignUpView>
- app/(auth)/org-selection/page.tsx (new) → delegates to <OrgSelectionView>
## Dashboard page
app/(dashboard)/page.tsx replaces the deleted app/page.tsx as the application
root. Shows UserButton, OrganizationSwitcher (hidePersonal), and the Convex
users table demo (useQuery + useMutation).
## Docs
- CHANGELOG.md updated with v0.4.0 section; compare link added
- README.md: Organizations added to Features; Architecture and Project
Structure sections updated to reflect modules/ and route groups
#35) feat(web): add Clerk organizations with module-based auth architecture
📝 WalkthroughWalkthroughThis PR introduces Clerk Organizations support via a new module-based auth architecture under ChangesOrganizations and module-based auth
Sequence Diagram(s)sequenceDiagram
participant User
participant ProxyMiddleware
participant AuthGuard
participant OrganizationGuard
participant ClerkAPI
User->>ProxyMiddleware: request protected route
ProxyMiddleware->>ClerkAPI: auth.protect()
alt no orgId and not org-free route
ProxyMiddleware-->>User: redirect to /org-selection
else has orgId or org-free route
ProxyMiddleware->>AuthGuard: pass through to page
AuthGuard->>ClerkAPI: check auth state
alt unauthenticated
AuthGuard-->>User: render SignInView
else authenticated
AuthGuard->>OrganizationGuard: render children
OrganizationGuard->>ClerkAPI: useOrganization()
alt no organization
OrganizationGuard-->>User: render OrgSelectionView
else has organization
OrganizationGuard-->>User: render dashboard children
end
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Tools execution failed with the following error: Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error) Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/web/proxy.ts (1)
4-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared auth-free route list.
The sign-in/sign-up patterns now live in two separate matchers. The next route change only needs one missed update to create a redirect bug, so it is safer to define the shared list once and extend it for org-free routes.
♻️ Proposed refactor
-const isPublicRoute = createRouteMatcher(["/sign-in(.*)", "/sign-up(.*)"]) - -const isOrgFreeRoute = createRouteMatcher([ - "/sign-in(.*)", - "/sign-up(.*)", - "/org-selection(.*)", -]) +const publicRoutes = ["/sign-in(.*)", "/sign-up(.*)"] + +const isPublicRoute = createRouteMatcher(publicRoutes) +const isOrgFreeRoute = createRouteMatcher([ + ...publicRoutes, + "/org-selection(.*)", +])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/proxy.ts` around lines 4 - 10, The auth-free route patterns are duplicated between isPublicRoute and isOrgFreeRoute, which makes future route changes easy to miss; extract the shared sign-in/sign-up matcher list into a single constant in proxy.ts and reuse it when defining both createRouteMatcher calls, adding the org-selection pattern only for the org-free matcher.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/app/`(dashboard)/page.tsx:
- Line 18: The Add button currently triggers addUser() without any in-flight
protection, so repeated clicks can submit duplicate api.users.add mutations.
Update the page component’s button handling in the dashboard page so the button
is disabled while the add mutation is pending, and make addUser() short-circuit
or otherwise respect that pending state. Use the existing addUser handler and
the Button component to gate clicks until the mutation settles.
In `@CHANGELOG.md`:
- Line 14: The changelog version heading does not match the PR’s stated
post-release sync target, so update the release section in CHANGELOG.md to use
the intended version consistently. Verify whether this change should be recorded
under v0.3.0 or v0.4.0, then align the top-level changelog entry and any related
release metadata with that same version to avoid release/tagging confusion.
---
Nitpick comments:
In `@apps/web/proxy.ts`:
- Around line 4-10: The auth-free route patterns are duplicated between
isPublicRoute and isOrgFreeRoute, which makes future route changes easy to miss;
extract the shared sign-in/sign-up matcher list into a single constant in
proxy.ts and reuse it when defining both createRouteMatcher calls, adding the
org-selection pattern only for the org-free matcher.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 71f43498-e149-4484-afa8-c3693f78cdc8
📒 Files selected for processing (16)
CHANGELOG.mdREADME.mdapps/web/app/(auth)/layout.tsxapps/web/app/(auth)/org-selection/[[...org-selection]]/page.tsxapps/web/app/(auth)/sign-in/[[...sign-in]]/page.tsxapps/web/app/(auth)/sign-up/[[...sign-up]]/page.tsxapps/web/app/(dashboard)/layout.tsxapps/web/app/(dashboard)/page.tsxapps/web/app/page.tsxapps/web/modules/auth/ui/components/auth-guard/index.tsxapps/web/modules/auth/ui/components/organization-guard/index.tsxapps/web/modules/auth/ui/layouts/auth-layout/index.tsxapps/web/modules/auth/ui/views/org-selection-view/index.tsxapps/web/modules/auth/ui/views/sign-in-view/index.tsxapps/web/modules/auth/ui/views/sign-up-view/index.tsxapps/web/proxy.ts
💤 Files with no reviewable changes (1)
- apps/web/app/page.tsx
| <p> apps/web</p> | ||
| <UserButton /> | ||
| <OrganizationSwitcher hidePersonal /> | ||
| <Button onClick={() => addUser()}>Add</Button> |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Gate the add mutation behind an in-flight state.
Line 18 fires api.users.add on every click and never disables the button, so double-clicks can enqueue multiple writes before the first mutation settles. That is risky on an add path.
♻️ Proposed fix
+import { useState } from "react"
import { useMutation, useQuery } from "convex/react"
import { OrganizationSwitcher, UserButton } from "`@clerk/nextjs`"
@@
export default function Page() {
const users = useQuery(api.users.getMany)
const addUser = useMutation(api.users.add)
+ const [isAdding, setIsAdding] = useState(false)
+
+ const handleAddUser = async () => {
+ if (isAdding) return
+ setIsAdding(true)
+ try {
+ await addUser()
+ } finally {
+ setIsAdding(false)
+ }
+ }
+
return (
@@
- <Button onClick={() => addUser()}>Add</Button>
+ <Button disabled={isAdding} onClick={handleAddUser}>
+ Add
+ </Button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Button onClick={() => addUser()}>Add</Button> | |
| <Button disabled={isAdding} onClick={handleAddUser}> | |
| Add | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/app/`(dashboard)/page.tsx at line 18, The Add button currently
triggers addUser() without any in-flight protection, so repeated clicks can
submit duplicate api.users.add mutations. Update the page component’s button
handling in the dashboard page so the button is disabled while the add mutation
is pending, and make addUser() short-circuit or otherwise respect that pending
state. Use the existing addUser handler and the Button component to gate clicks
until the mutation settles.
|
|
||
| --- | ||
|
|
||
| ## [0.4.0] - 2026-07-01 |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major
Version mismatch between PR objectives and changelog.
The PR objectives describe this as a post-release synchronization for v0.3.0, but the changelog introduces a v0.4.0 release section. Please confirm the intended version — this discrepancy could cause confusion in release automation and tagging.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` at line 14, The changelog version heading does not match the
PR’s stated post-release sync target, so update the release section in
CHANGELOG.md to use the intended version consistently. Verify whether this
change should be recorded under v0.3.0 or v0.4.0, then align the top-level
changelog entry and any related release metadata with that same version to avoid
release/tagging confusion.
Bring the post-release sync merge commit onto main so main and develop are at the same commit level. No code changes — this is a pure history alignment commit.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation